﻿using Opc.Ua;
using SD.OpcServer.Models.Base;
using SD.OpcServer.Models.Entities;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace SD.OpcServer.Host.Variables
{
    /// <summary>
    /// OPC变量
    /// </summary>
    public class OpcVariable : BaseDataVariableState
    {
        #region # 构造器

        /// <summary>
        /// 创建代理变量构造器
        /// </summary>
        /// <param name="variableNo">变量编号</param>
        /// <param name="variableName">变量名称</param>
        /// <param name="dataType">数据类型</param>
        /// <param name="unit">单位</param>
        /// <param name="keepRecord">是否记录历史</param>
        /// <param name="description">描述</param>
        /// <param name="valueDescriptions">值描述字典</param>
        /// <param name="variableGroup">变量组</param>
        public OpcVariable(string variableNo, string variableName, string dataType, string unit, bool keepRecord, string description, IDictionary<string, string> valueDescriptions, OpcVariableGroup variableGroup)
            : base(variableGroup)
        {
            this.Number = variableNo;
            this.Name = variableName;
            this.Unit = unit;
            this.KeepRecord = keepRecord;
            this.ValueDescriptions = valueDescriptions ?? new Dictionary<string, string>();
            base.SymbolicName = variableNo;
            base.ReferenceTypeId = ReferenceTypes.Organizes;
            base.TypeDefinitionId = VariableTypeIds.BaseDataVariableType;
            base.NodeId = new NodeId($"{variableGroup.SymbolicName}.{variableNo}");
            base.BrowseName = new QualifiedName(variableNo);
            base.DisplayName = new LocalizedText(variableNo);
            base.Description = new LocalizedText(description);
            base.WriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description;
            base.UserWriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description;
            base.DataType = DataTypeIds.BaseDataType;
            base.ValueRank = ValueRanks.Scalar;
            base.AccessLevel = AccessLevels.CurrentReadOrWrite;
            base.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
            base.Historizing = false;
            base.StatusCode = StatusCodes.Good;
            base.Timestamp = DateTime.Now;

            object defaultValue = Constants.TypeDefaultValues[dataType];
            base.Value = defaultValue;

            variableGroup.AddChild(this);

            //值写入事件
            this.OnWriteValue = this.WriteValue;
        }

        #endregion

        #region # 属性

        #region 变量编号 —— string Number
        /// <summary>
        /// 变量编号
        /// </summary>
        public string Number { get; set; }
        #endregion

        #region 变量名称 —— string Name
        /// <summary>
        /// 变量名称
        /// </summary>
        public string Name { get; set; }
        #endregion

        #region 单位 —— string Unit
        /// <summary>
        /// 单位
        /// </summary>
        public string Unit { get; set; }
        #endregion

        #region 是否记录历史 —— bool KeepRecord
        /// <summary>
        /// 是否记录历史
        /// </summary>
        public bool KeepRecord { get; set; }
        #endregion

        #region 值描述字典 —— IDictionary<string, string> ValueDescriptions
        /// <summary>
        /// 值描述字典
        /// </summary>
        /// <remarks>键：变量值，值：变量值描述</remarks>
        public IDictionary<string, string> ValueDescriptions { get; set; }
        #endregion

        #region 只读属性 - 变量组 —— OpcVariableGroup OpcVariableGroup
        /// <summary>
        /// 变量组
        /// </summary>
        public OpcVariableGroup OpcVariableGroup
        {
            get { return (OpcVariableGroup)this.Parent; }
        }
        #endregion

        #endregion

        #region # 方法

        #region 值写入事件 —— ServiceResult WriteValue(ISystemContext context...
        /// <summary>
        /// 值写入事件
        /// </summary>
        private ServiceResult WriteValue(ISystemContext context, NodeState node, NumericRange indexRange, QualifiedName dataEncoding, ref object value, ref StatusCode statusCode, ref DateTime timestamp)
        {
            if (node is OpcVariable opcVariable)
            {
                opcVariable.SetValue(value);
            }

            return new ServiceResult(statusCode);
        }
        #endregion

        #region 赋值 —— void SetValue(object value)
        /// <summary>
        /// 赋值
        /// </summary>
        /// <param name="value">值</param>
        private void SetValue(object value)
        {
            string dataType = value?.GetType().FullName;
            string previousValue = this.Value?.ToString();
            string currentValue = value?.ToString();

            #region # 验证

            if (string.IsNullOrWhiteSpace(currentValue))
            {
                return;
            }
            if (currentValue == Constants.Nil)
            {
                return;
            }
            if (this.KeepRecord && previousValue != currentValue)
            {
                //记录
                Task.Run(() => this.LogDataChange(this, dataType, previousValue, currentValue));
            }

            #endregion

            base.Value = currentValue;
            base.Timestamp = DateTime.Now;
            base.ClearChangeMasks(this.OpcVariableGroup.NodeManager.SystemContext, false);
        }
        #endregion

        #region 记录数据变更 —— void LogDataChange(ProxyVariable proxyVariable...
        /// <summary>
        /// 记录数据变更
        /// </summary>
        private void LogDataChange(OpcVariable opcVariable, string dataType, string previousValue, string currentValue)
        {
            using (DbSession dbSession = new DbSession())
            {
                string previousValueDescription;
                string currentValueDescription;

                #region # 值描述取值

                const string initialNull = "初始空值";
                const string connectInterruptd = "连接中断";

                if (previousValue == null)
                {
                    previousValueDescription = initialNull;
                }
                else if (previousValue == Constants.Nil)
                {
                    previousValueDescription = connectInterruptd;
                }
                else if (opcVariable.ValueDescriptions.ContainsKey(previousValue))
                {
                    previousValueDescription = opcVariable.ValueDescriptions[previousValue];
                }
                else
                {
                    previousValueDescription = null;
                }
                if (currentValue == Constants.Nil)
                {
                    currentValueDescription = connectInterruptd;
                }
                else if (currentValue == null)
                {
                    currentValueDescription = initialNull;
                }
                else if (opcVariable.ValueDescriptions.ContainsKey(currentValue))
                {
                    currentValueDescription = opcVariable.ValueDescriptions[currentValue];
                }
                else
                {
                    currentValueDescription = null;
                }

                #endregion

                VariableHistory record = new VariableHistory(opcVariable.Number, opcVariable.Name, dataType, previousValue, previousValueDescription, currentValue, currentValueDescription, opcVariable.Unit);

                dbSession.Set<VariableHistory>().Add(record);
                dbSession.SaveChanges();
            }
        }
        #endregion 

        #endregion
    }
}
